Dash Anomalías de temperatura#

La apliclación desplegada tiene como objetivo de visualización los cambios de temperatura cercana a la superficie en todo el mundo, entre 1850 a 2018. El archivo presenta anomalías mensuales promedio de temperatura cercana a la superficie, en relación con el período 1961–1990.

En general, este conjunto de datos muestra un aumento del calentamiento promedio global desde mediados del siglo 19 y en los últimos años, en consonancia con otros análisis. Este fenómeno se debe a múltiples factores, incluidos una mejor representación del calentamiento del Ártico y una mejor comprensión de los sesgos en evolución en las mediciones sobre la superficie del mar y la tierra. Para revisar a detalle el proceso de obtención y procesamiento de los datos, visite la información de referencia al final de este documento.

# Importanción de librerías

import pandas as pd
from time import mktime
from datetime import datetime, timedelta
import time
import seaborn as sns
import matplotlib.pyplot as plt
import pickle
import warnings
warnings.filterwarnings("ignore")

Manejo de la base de datos#

  • Previo a la construcción de la aplicación, es necesario procesar la base de datos. En este caso, utilizamos la librería pandas para leer los datos y configurar la estructura para que esté acorde con el formato de series de tiempo.

# Leer los datos
temperature_URL = "https://raw.githubusercontent.com/SandraMaldonado19/Dash_PF_Dataviz/main/originaldata_temp.csv"
df_temperature = pd.read_csv(temperature_URL)

df_temperature.head()
year january february march april may june july august september october november december yearmean Unnamed: 14
0 1850 -0.675 -0.333 -0.591 -0.589 -0.508 -0.344 -0.160 -0.208 -0.385 -0.533 -0.283 -0.404 -0.418 NaN
1 1850 53.000 47.000 44.000 48.000 49.000 49.000 52.000 55.000 57.000 55.000 58.000 58.000 NaN NaN
2 1851 -0.201 -0.469 -0.646 -0.542 -0.198 -0.137 -0.097 -0.102 -0.091 -0.008 -0.082 -0.228 -0.233 NaN
3 1851 58.000 55.000 56.000 55.000 54.000 53.000 54.000 59.000 47.000 51.000 48.000 48.000 NaN NaN
4 1852 -0.375 -0.477 -0.560 -0.585 -0.127 -0.084 0.005 -0.136 -0.002 -0.172 -0.305 0.065 -0.229 NaN
# Eliminar columna vacía

df_temperature = df_temperature.drop(df_temperature.columns[df_temperature.columns.str.contains('Unnamed')], axis=1)
df_temperature.head()
year january february march april may june july august september october november december yearmean
0 1850 -0.675 -0.333 -0.591 -0.589 -0.508 -0.344 -0.160 -0.208 -0.385 -0.533 -0.283 -0.404 -0.418
1 1850 53.000 47.000 44.000 48.000 49.000 49.000 52.000 55.000 57.000 55.000 58.000 58.000 NaN
2 1851 -0.201 -0.469 -0.646 -0.542 -0.198 -0.137 -0.097 -0.102 -0.091 -0.008 -0.082 -0.228 -0.233
3 1851 58.000 55.000 56.000 55.000 54.000 53.000 54.000 59.000 47.000 51.000 48.000 48.000 NaN
4 1852 -0.375 -0.477 -0.560 -0.585 -0.127 -0.084 0.005 -0.136 -0.002 -0.172 -0.305 0.065 -0.229
# Eliminar filas intermedias
df_temperature = df_temperature.drop(df_temperature.index[1::2]).reset_index(drop=True)
df_temperature.head()
year january february march april may june july august september october november december yearmean
0 1850 -0.675 -0.333 -0.591 -0.589 -0.508 -0.344 -0.160 -0.208 -0.385 -0.533 -0.283 -0.404 -0.418
1 1851 -0.201 -0.469 -0.646 -0.542 -0.198 -0.137 -0.097 -0.102 -0.091 -0.008 -0.082 -0.228 -0.233
2 1852 -0.375 -0.477 -0.560 -0.585 -0.127 -0.084 0.005 -0.136 -0.002 -0.172 -0.305 0.065 -0.229
3 1853 -0.233 -0.404 -0.280 -0.386 -0.268 -0.142 -0.083 -0.057 -0.250 -0.392 -0.411 -0.337 -0.270
4 1854 -0.381 -0.361 -0.243 -0.334 -0.291 -0.299 -0.179 -0.239 -0.217 -0.095 -0.410 -0.450 -0.292
  • Aquí colocamos los meses que se encuentra en las filas como variables independientes en una sola columna, con su respectivo valor de anomalía, usando la función melt.

# Convertir a formato de serie de tiempo
df_timeseries = pd.melt(df_temperature, id_vars=['year'])

# Eliminar filas donde la columna 'variable' sea igual a 'yearmean'
df_timeseries = df_timeseries[df_timeseries['variable'] != 'yearmean']

# Cambiar el nombre de la columna 'variable' a 'month'
df_timeseries = df_timeseries.rename(columns={'variable':'month'})

df_timeseries.head()
year month value
0 1850 january -0.675
1 1851 january -0.201
2 1852 january -0.375
3 1853 january -0.233
4 1854 january -0.381
# Crear la nueva columna de fecha en formato %Y-%m
df_timeseries['date'] = pd.to_datetime(df_timeseries['year'].astype(str) + '-' + df_timeseries['month'], format='%Y-%B')

# Formatear la fecha para que solo muestre mes y año
df_timeseries['date'] = df_timeseries['date'].dt.strftime('%Y-%m')
df_timeseries
year month value date
0 1850 january -0.675 1850-01
1 1851 january -0.201 1851-01
2 1852 january -0.375 1852-01
3 1853 january -0.233 1853-01
4 1854 january -0.381 1854-01
... ... ... ... ...
2083 2019 december 1.037 2019-12
2084 2020 december 0.693 2020-12
2085 2021 december 0.751 2021-12
2086 2022 december 0.768 2022-12
2087 2023 december -9.999 2023-12

2088 rows × 4 columns

df_timeseries = df_timeseries.sort_values(by=['date'])

# Eliminar las últimas tres filas
df_timeseries = df_timeseries.iloc[:-3]
df_timeseries
year month value date
0 1850 january -0.675 1850-01
174 1850 february -0.333 1850-02
348 1850 march -0.591 1850-03
522 1850 april -0.589 1850-04
696 1850 may -0.508 1850-05
... ... ... ... ...
869 2023 may 0.871 2023-05
1043 2023 june 1.052 2023-06
1217 2023 july 1.150 2023-07
1391 2023 august 1.199 2023-08
1565 2023 september 1.347 2023-09

2085 rows × 4 columns

df_timeseries.to_csv("df_timeseries.csv", index=False)

Análisis Exploratorio de Datos - EDA#

Aquí se presenta un análisis de los datos. Incluye gráficos de la serie de tiempo en diversas escalas temporales (mensual, anual, decenal), gráficos de línea, gráficos de caja y bigotes, mapas de calor, proporcionando información sobre patrones y tendencias de la variable objetivo (Anomalía de temperatura (°C)) de forma interactiva. Asimismo, se explora la estacionariedad de la serie, información base para la construcción de modelos predictivos.

timeseries_URL = "https://raw.githubusercontent.com/SandraMaldonado19/Dash_PF_Dataviz/main/df_timeseries.csv"
df_tseries = pd.read_csv(timeseries_URL)

Media móvil de 12 meses#

  • El código inicia importando las bibliotecas necesarias y cargando un conjunto de datos desde un repositorio en github. Luego, define una función para calcular la media móvil y el intervalo de confianza de los datos. La aplicación Dash se crea y se diseña con un título y el gráfico de la serie de tiempo. Una función callback se define para actualizar el gráfico cuando el usuario coloca el cursor sobre él con la función ‘hover’, mostrando la media móvil, el intervalo de confianza y la media del año seleccionado. Finalmente, la aplicación se ejecuta.

import pandas as pd
import plotly.graph_objects as go
import plotly.io as pio
from plotly.offline import iplot


# Definimos la función encargada de calcular la media móvil y el intervalo de 
## En este código '1.96' es el valor z para un intervalo de confianza del 95%.

# Función para calcular la media móvil y el intervalo de confianza
def calculate_rolling_mean(df, window_size=12, num_of_std=1.96):
    df['rolling_mean'] = df['value'].rolling(window=window_size).mean()
    df['rolling_std'] = df['value'].rolling(window=window_size).std()
    df['upper_band'] = df['rolling_mean'] + (df['rolling_std'] * num_of_std)
    df['lower_band'] = df['rolling_mean'] - (df['rolling_std'] * num_of_std)
    return df


df_timeseries = calculate_rolling_mean(df_tseries.copy())

# Crear la figura
fig = go.Figure()

# Agregar la serie temporal original
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['value'], mode='lines', name='Serie original', line=dict(color='blue')))

# Agregar la media móvil
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['rolling_mean'], mode='lines', name='12-Month Rolling Mean', line=dict(color='red')))

# Agregar el intervalo de confianza
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['upper_band'], mode='lines', name='Banda superior (95%)', line=dict(dash='dash', color='green')))
fig.add_trace(go.Scatter(x=df_timeseries['date'], y=df_timeseries['lower_band'], mode='lines', name='Banda inferior (95%)', line=dict(dash='dash', color='green')))

# Configurar el layout
fig.update_layout(title='Media Móvil de 12 Meses con Intervalo de Confianza', xaxis_title='Fecha', yaxis_title='Anomalía de Temperatura (°C)')

# Mostrar la figura
iplot(fig)
  • Se puede observar una tendencia de aumento en la serie luego de la etapa pre-industrial, lo que sugiere que la serie tiene un comportamiento no estacionario, con medias variables y en aumento para intervalos de 12 meses.

Media mensual multianual y distribución de datos: Boxplot#

  • Se graficó un boxplot Multianual por mes, que se utiliza para visualizar la distribución de los datos de Anomalías de Temperatura a lo largo de los meses del año. Cada caja en el gráfico representa un mes específico y muestra la variabilidad de los datos para ese mes a lo largo de varios años. El gráfico está diseñado de tal manera que cada caja (o ‘boxplot’) muestra la mediana (la línea en el medio de la caja), los cuartiles superior e inferior (los bordes superior e inferior de la caja), y los valores atípicos (los puntos por encima y por debajo de las líneas que se extienden desde la caja). Esto proporciona una visión clara de la distribución de los datos, permitiendo identificar tendencias y anomalías.

import plotly.graph_objects as go
import pandas as pd

# Convert 'month' to categorical type with correct order
ordered_months = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']
df_tseries['month'] = pd.Categorical(df_tseries['month'], categories=ordered_months, ordered=True)

# Create boxplots for each month
fig = go.Figure([go.Box(x=data['month'], y=data['value'], name=month, width=0.5) for month, data in df_tseries.groupby('month')])

# Add trend line (multi-year average)
trend_line = df_tseries.groupby('month')['value'].mean().reset_index()
fig.add_trace(go.Scatter(x=trend_line['month'], y=trend_line['value'],
                         mode='lines', line=dict(color='black', dash='dash'),
                         name='Media Multianual'))

# Adjust the layout of the figure
fig.update_layout(xaxis={'title': 'Mes'}, yaxis={'title': 'Anomalía de Temperatura'},
                  boxmode='group')  # To group the boxplots on the x-axis

# Display the figure
fig.show()
  • El análisis de Anomalías de temperatura por meses indica que, por lo menos el 50% de los datos para todos los meses, se mantiene por debajo de las temperaturas preindustriales, con magnitudes que varían entre -0.22°C y -0.09°C, siendo el mes de octubre el que presenta temperaturas más similares a este período de referencia. Se conoce que, por lo menos el 25% superior de los datos presenta aumentos de temperatura para todos los meses.

Anomalías mensual-anual: mapa de calor#

# Meses ordenados
month_order = ['january', 'february', 'march', 'april', 'may', 'june', 'july', 'august', 'september', 'october', 'november', 'december']

heatmap_data = df_tseries.pivot(index='month', columns='year', values='value')
heatmap_data = heatmap_data.reindex(month_order)


fig = go.Figure()
fig.add_trace(go.Heatmap(x=heatmap_data.columns, y=heatmap_data.index, z=heatmap_data.values,
                         colorscale='RdBu', colorbar=dict(title='Anomalía de Temperatura'), reversescale=True))
fig.update_layout(xaxis={'title': 'Año'}, yaxis={'title': 'Mes', 'categoryorder': 'array', 'categoryarray': month_order},
                  title='Mapa de Calor - Anomalías de Temperatura (°C)')

# Display the figure
fig.show()
  • A partir de este mapa de calor se han identificado tendencias significativas en los datos de temperatura. Tras la Revolución Industrial, que tuvo lugar a finales del siglo XIX, se ha registrado un incremento progresivo en las temperaturas hasta el cierre del siglo XX. Con el inicio del nuevo milenio, se ha observado una aceleración notable en este incremento de temperatura. Estos patrones globales coinciden con fenómenos de gran escala como la proliferación de la industrialización y el auge y desarrollo de las áreas urbanas, que son los principales contribuyentes a la emisión de gases de efecto invernadero (GEI) a gran escala. Estos gases son los causantes primordiales del calentamiento global debido a su producción intensiva.

Promedio decenal#

  • A continuación, se crea un gráfico de barras. Se define una función de devolución de llamada que se activa cuando el usuario interactúa con el gráfico de barras. Esta función calcula los promedios decenales y actualiza el gráfico de barras.

import pandas as pd
import plotly.express as px
import numpy as np

# Convertir la columna 'date' a tipo datetime si aún no lo está
df_tseries['date'] = pd.to_datetime(df_tseries['date'])

# Agregar una columna 'decade' para agrupar por década
df_tseries['decade'] = (df_tseries['year'] // 10) * 10

# Agregar una columna 'color' basada en la condición de la anomalía
df_tseries['color'] = df_tseries['value'].apply(lambda x: 'red' if x > 0 else 'blue')

# Calcular los promedios decenales
decade_means = df_tseries.groupby('decade').agg({'value': 'mean', 'color': 'first'}).reset_index()

# Crear el gráfico de barras con Plotly Express
fig = px.bar(decade_means, x='decade', y='value', color='color',
                 labels={'value': 'Anomalía de Temperatura'}, 
                 color_discrete_map={'red': 'red', 'blue': 'blue'},
                 title='Promedios Decenales de Anomalías de Temperatura')

    # Modificar las etiquetas de los ticks en el eje x
fig.update_layout(
        xaxis=dict(
            tickmode='array',
            tickvals=decade_means['decade'],
            ticktext=[f"{decade}s" for decade in decade_means['decade']]
        ),
        showlegend=False
    )
  • La gráfica es consistente con el mapa de calor anterior, mostrando durante el siglo XX, especialmente entre 1940 y 1980, se empiezan a ver estas tendencias de aumento de la temperatura.

Visualización de máximos anuales#

  • El siguiente gráfico agrega líneas con la tendencia mensual de la temperatura de un año específico que seleccione el ususario. El usuario puede comparar cualquier año de preferencia con los años que poseen mayores aumentos; en esta caso, corresponden a 2016, 2019 y 2020.

import plotly.graph_objs as go
import pandas as pd
import numpy as np
from datetime import datetime

# Asegúrate de que los datos estén en el formato correcto
df_tseries['date'] = pd.to_datetime(df_tseries['date'])
df_tseries['year'] = df_tseries['date'].dt.year
df_tseries['month'] = df_tseries['date'].dt.month

# Encuentra los tres años más cálidos, excluyendo el año actual
current_year = datetime.now().year
top_years = df_tseries[df_tseries['year'] != current_year].groupby('year')['value'].mean().nlargest(3).index

# Encuentra los valores máximos para cada mes
max_values = df_tseries.groupby('month')['value'].max()

traces = []
markers = ['circle', 'square', 'diamond', 'cross', 'x']

# Añade las líneas para los años más cálidos
for i, year in enumerate(top_years):
    df_year = df_tseries[df_tseries['year'] == year]
    traces.append(go.Scatter(
        x=df_year['month'],
        y=df_year['value'],
        mode='lines+markers',
        name=str(year),
        marker=dict(
            symbol=markers[i % len(markers)],
            size=10
        )
    ))

# Añade las líneas para los años seleccionados
selected_years = [df_tseries['year'].max()]
for i, selected_year in enumerate(selected_years):
    df_selected = df_tseries[df_tseries['year'] == selected_year]
    traces.append(go.Scatter(
        x=df_selected['month'],
        y=df_selected['value'],
        mode='lines+markers',
        name=str(selected_year),
        line={'color': 'black', 'width': 2},
        marker=dict(
            symbol=markers[(i + len(top_years)) % len(markers)],
            size=10
        )
    ))

# Añade la línea para los valores máximos
traces.append(go.Scatter(
    x=np.arange(1, 13),
    y=max_values,
    mode='lines',
    name='Máximo histórico',
    line={'color': 'darkgrey', 'width': 2.5}
))

fig = go.Figure(data=traces)
fig.update_layout(
    xaxis={'title': 'Mes', 'tickvals': list(range(1, 13)), 'ticktext': ['Enero', 'Febrero', 'Marzo', 'Abril', 'Mayo', 'Junio', 'Julio', 'Agosto', 'Septiembre', 'Octubre', 'Noviembre', 'Diciembre']},
    yaxis={'title': 'Anomalía de temperatura (°C)'},
    hovermode='closest'
)
fig.show()

Verificación de la estacionariedad de la serie#

  • Se utiliza la prueba estadística Dickey-Fuller y los plots ACF y PACF para verificar si la serie es estacionaria. Esto es indispensable para construir modelos de series temporales, como los modelos ARIMA, ya que la estacionariedad es un supuesto fundamental. Si las propiedades estadísticas de la serie no cambian a lo largo del tiempo, se pueden obtener predicciones más precisas.

import numpy as np, pandas as pd
from statsmodels.graphics.tsaplots import plot_acf
from statsmodels.tsa.stattools import adfuller
import matplotlib.pyplot as plt
result = adfuller(df_tseries.value)
adf_statistic = result[0]
p_value = result[1]
print("ADF:", adf_statistic)
print("p-value:", p_value)
ADF: -0.09427041182601259
p-value: 0.9500062407464049
  • Las tendencias en la serie muestran que la series no es estacionaria. Por lo tanto, debe aplicarse diferenciación para convertir la serie en estacionaria.

#----- Autocorrelación--------------------------------------------------------------------------------------------------------
import numpy as np, pandas as pd
from statsmodels.graphics.tsaplots import plot_acf
import matplotlib.pyplot as plt
from plotly.subplots import make_subplots
from statsmodels.tsa.stattools import acf


# Crear una figura con subplots
fig = make_subplots(rows=3, cols=2)

# Agregar los gráficos a los subplots correspondientes
fig.add_trace(go.Scatter(y=df_tseries.value, mode='lines', line=dict(color='blue'), showlegend=False, hovertemplate='Value: %{y}'), row=1, col=1)
fig.add_trace(go.Scatter(y=df_tseries.value.diff(), mode='lines', line=dict(color='red'), showlegend=False, hovertemplate='Diff: %{y}'), row=2, col=1)
fig.add_trace(go.Scatter(y=df_tseries.value.diff().diff(), mode='lines', line=dict(color='green'), showlegend=False, hovertemplate='Diff Diff: %{y}'), row=3, col=1)

# Para agregar los gráficos de autocorrelación, se necesita calcular los valores de autocorrelación
acf_values = acf(df_tseries.value, nlags=2080)
fig.add_trace(go.Scatter(y=acf_values, mode='lines', fill='tozeroy', line=dict(color='blue'), showlegend=False, hovertemplate='ACF: %{y}'), row=1, col=2)

diff1_values = df_tseries.value.diff()
acf_diff1_values = acf(diff1_values.dropna(), nlags=2080)
fig.add_trace(go.Scatter(y=acf_diff1_values, mode='lines', fill='tozeroy', line=dict(color='red'), showlegend=False, hovertemplate='ACF Diff: %{y}'), row=2, col=2)

diff2_values = df_tseries.value.diff().diff()
acf_diff2_values = acf(diff2_values.dropna(), nlags=2080)
fig.add_trace(go.Scatter(y=acf_diff2_values, mode='lines', fill='tozeroy', line=dict(color='green'), showlegend=False, hovertemplate='ACF Diff Diff: %{y}'), row=3, col=2)

# Agregar títulos a las gráficas

fig.add_annotation(dict(text='Serie original', x=0.20, y=1.1, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='1er Orden de diferenciación', x=0.15, y=0.66, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='2do Orden de diferenciación', x=0.15, y=0.25, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))

fig.add_annotation(dict(text='ACF de la Serie original', x=0.85, y=1.1, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='ACF del 1er Orden de diferenciación', x=0.88, y=0.66, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
fig.add_annotation(dict(text='ACF del 2do Orden de diferenciación', x=0.88, y=0.25, xref='paper', yref='paper', showarrow=False, font=dict(size=14)))
  • El gráfico indica que con un orden de diferenciación la serie se volvió en más estacionaria. Para comprobar la estacionariedad, aplicamos la prueba ADF nuevamente.

from statsmodels.tsa.stattools import adfuller

# Aplica la función diff() una vez y elimina los valores NaN
diff_values = df_tseries.value.diff().dropna()

result = adfuller(diff_values)
adf_statistic = result[0]
p_value = result[1]

print("ADF:", adf_statistic)
print("p-value:", p_value)
ADF: -15.697771241158453
p-value: 1.408231089800588e-28
  • Dado que p-value < 0.05, se comprueba que la serie es estacionaria aplicando un orden de diferenciación.